---
slug: how-to-handle-modules
title: 如何处理模块的？
authors: [丁少华]
tags: [webpack底层处理]
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CollapseCodeblock from "/src/components/CollapseCodeblock";

无论在node中 还是在浏览器中，由于兼容性考虑，我们都无法随心所欲的使用任意的模块化语法。但是通过现代化的打包工具便可以解决这些问题，如webpack、rollup、esbuld等。

这里我们重点聊聊webpack，通过分析打包后的代码，我们来分析一下它的实现兼容性原理。




## 处理CJS

### module.exports
趁热打铁，我们在通过两个例子熟悉下这个过程

有个简单的,使用了`module.exports`和`require` 示例代码如下

<Tabs>
<TabItem value="index.js" label="index.js">

```js
const add = require("./add.js");

const res = add(1, 2);
console.log(res);
```

</TabItem>
<TabItem value="add.js" label="add.js">

```js
module.exports = (x, y) => {
    return x + y;
};
```

</TabItem>
</Tabs>

在`mode`为`development`下，我们分析打包后代码，并以此为依据推导出webpack是如何处理模块。   
另外，为了提高阅读性，我们暂时先关闭开发者工具 `devtool: false`   
除此之外,为了方便解读，我把无用的代码和备注都删除了

<Tabs>
<TabItem value="优化阅读版" label="优化阅读版">

```js showLineNumbers
// sucess-start
/*
*模块定义
项目中用到的模块都会被集中到这里定义
*/
var __webpack_modules__ = {
  "./src/add.js"(module) {
    module.exports = (x, y) => {
      return x + y;
    };
  }
};
// sucess-end

// sucess-start
/*
* require 方法定义
* __webpack_require__ webpack 自己实现 require 方法，
* 浏览器是默认不支持 commonJS，所以需要自己实现一个函数
*/
var __webpack_module_cache__ = {}; // 模块缓存,用于优化内存
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  __webpack_modules__[moduleId](module);
  return module.exports;
}
// sucess-end

// sucess-start
/*
* 入口文件
* 程序的执行入口,相当于java的main方法
*/
(() => {
  const add = __webpack_require__("./src/add.js");
  const res = add(1, 2);
  console.log(res);
})(); 
// sucess-end
```

</TabItem>
<TabItem value="打包输出原版" label="打包输出原版">

```js
/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ "./src/add.js":
/*!********************!*\
  !*** ./src/add.js ***!
  \********************/
/***/ ((module) => {

module.exports = (x, y) => {
    return x + y;
};;

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
const add = __webpack_require__(/*! ./add.js */ "./src/add.js");

const res = add(1, 2);
console.log(res);
})();

/******/ })();
```

</TabItem>
</Tabs>


**🤔分析下🤔**    
总结下就是:    
程序从入口文件开始执行(35-39行)，导入`add.js`模块, 引入完成后继续执行入口文件的代码      
重点是导入模块，这里webpack做了内部的处理--->因为浏览器并不支持模块化

webpack做了内部怎么处理模块化的呢?   
webpack将所有的模块到放到它定义的一个`__webpack_modules__` 对象中，加载模块时候通过`__webpack_require__`方法来从其中加载模块   
除此之外，webpack还对`__webpack_require__`方法做了优化，加入了缓存能力






### exports
commonjs的导出除了`module.exports`还有`exports.xx`(两者不能共同出现使用)
<Tabs>
<TabItem value="index.js" label="index.js">

```js
const {add} = require("./utils.js");

const res = add(1, 2);
console.log(res);
```

</TabItem>
<TabItem value="add.js" label="add.js">

```js
exports.add = (x, y) => {
    return x + y;
};
```

</TabItem>
</Tabs>


和module.exports打包后的代码 略有不同，体现在第6行和27行   
多传递了一个参数(exports)因为模块内部有使用
```js showLineNumbers
/*
*模块定义
项目中用到的模块都会被集中到这里定义
*/
var __webpack_modules__ = {
   // warn-start
  "./src/utils.js"(__unused_webpack_module, exports) {
     // warn-end
      exports.add = (x, y) => {
        return x + y;
      };
    }
};

/*
* require 方法定义
* __webpack_require__ webpack 自己实现 require 方法，
* 浏览器是默认不支持 commonJS，所以需要自己实现一个函数
*/
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  // warn-start
  __webpack_modules__[moduleId](module, module.exports);
  // warn-end
  return module.exports;
}

/*
* 入口文件
* 程序的执行入口,相当于java的main方法
*/
(() => {
  const { add } = __webpack_require__("./src/utils.js");
  const res = add(1, 2);
  console.log(res);
})();

```


### 多模块打包
在大型应用中，不可能只存在入口文件依赖并导入某一个模块文件就完事了      
现实中模块的依赖是复杂的: 如存在多个模块导入入口文件、或者入口文件依赖导入a模块，a模块又依赖并导入b模块等复杂情况!
<Tabs>
<TabItem value="index.js" label="index.js">

```js
const add = require("./add.js");

const res = add(1, 2);
console.log(res);
```

</TabItem>
<TabItem value="add.js" label="add.js">

```js
const time = require('./time.js')

module.exports = (x, y) => {
    return {
        time: time(),
        result: x + y
    };
};
```

</TabItem>
<TabItem value="time.js" label="time.js">

```js
module.exports = ()=>{
    return new Date();
}
```

</TabItem>
</Tabs>


同样，区别也是体现在第6行和36行   
多传递了一个参数(__webpack_require__)因为模块内部有使用
```js showLineNumbers
/*
*模块定义
项目中用到的模块都会被集中到这里定义
*/
var __webpack_modules__ = {
    // warn-start
  "./src/add.js"(module, __unused_webpack_exports, __webpack_require__) {
    // warn-end
    const time = __webpack_require__("./src/time.js")
    module.exports = (x, y) => {
      return {
        time: time(),
        result: x + y
      };
    };
  },
  "./src/time.js"(module) {
    module.exports = () => {
      return new Date();
    }
  }
};

/*
* require 方法定义
* __webpack_require__ webpack 自己实现 require 方法，
* 浏览器是默认不支持 commonJS，所以需要自己实现一个函数
*/
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  // warn-start
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  // warn-end
  return module.exports;
}

/*
* 入口文件
* 程序的执行入口,相当于java的main方法
*/
(() => {
  const add = __webpack_require__("./src/add.js");
  const res = add(1, 2);
  console.log(res);
})();
```



## 处理ESM
webpack可以处理任意模块方案，除了node衍生的commonjs模块方案之外，最新的官方ems也支持!
<Tabs>
<TabItem value="index.js" label="index.js">

```js
import utils, {add} from './utils.js'

const res = add(1, 2);
console.log(res, utils);
```

</TabItem>
<TabItem value="utils.js" label="utils.js">

```js 
export const add = (x, y) => {
    return x + y;
};

export default '工具';
```

</TabItem>
</Tabs>



来让我们看看编译之后的代码
```js showLineNumbers
// sucess-start
/*
*模块定义
项目中用到的模块都会被集中到这里定义
*/
var __webpack_modules__ = {
  "./src/utils.js": (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    const add = (x, y) => { return x + y; };
    const __WEBPACK_DEFAULT_EXPORT__ = '工具';
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, {
      add: () => (add),
      default: () => (__WEBPACK_DEFAULT_EXPORT__)
    });
  }
};
// sucess-end

// sucess-start
/*
* require 方法定义
* __webpack_require__ webpack 自己实现 require 方法，
* 旧浏览器是默认不支持 esm，所以需要自己实现一个函数
*/
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
}
__webpack_require__.d = (exports, definition) => {
  for (var key in definition) {
    if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
      Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
    }
  }
};
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
__webpack_require__.r = (exports) => {
  if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  }
  Object.defineProperty(exports, '__esModule', { value: true });
};
// sucess-end

// sucess-start
/*
* 入口文件
* 程序的执行入口,相当于java的main方法
*/
(() => {
  var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils.js");
  const res = (0, _utils_js__WEBPACK_IMPORTED_MODULE_0__.add)(1, 2);
  console.log(res, _utils_js__WEBPACK_IMPORTED_MODULE_0__["default"]);
})();
// sucess-end
```


**🤔分析下🤔**  
:::tip 
使用 ES6 module 规范打包后的代码和使用 CommonJS 规范打包后的代码绝大部分都是一样的,编译后的代码也是分成3个部分：模块、模块处理方法、和入口代码。但是细节实现又略有不同！
:::


模块定义那里（5-15行），与commonjs不同的是 ems编译后的代码并没有将模块内部内容直接挂载到对应的模块导出对象export上， 而是通过`__webpack_require__.d`方法来实现定义导出的变量到exports对象上。   
```js
__webpack_require__.d(__webpack_exports__, {
  add: () => (add),
  default: () => (__WEBPACK_DEFAULT_EXPORT__)
});
// 它的作用相当于如下 
__webpack_exports__.add=() => (add)
__webpack_exports__["default"] = (__WEBPACK_DEFAULT_EXPORT__)
```

另外，`__webpack_require__.o`方法就是一个封装的一个 判断属性是否存在对象上的工具函数。

`__webpack_require__.r` 用于给给当前模块的导出变量（整个模块 [* as all中的all]）打标识，去标识导入规范类型和当前环境是否支持es6语法，即表示这是一个 ES6 module。




## 异步加载
异步加载也叫做懒加载或者按需加载，是一种很好的优化网页或应用的方式。   
懒加载的本质实际上就是代码分离，把代码分离到不同 bundle 中，再按需加载或并行加载这些文件。   
这样加快了应用的初始加载速度，减轻了它的总体体积，因为某些代码块可能永远不会被加载。

在Webpack中常用的代码分离方法有三种 入口起点、防止重复、动态导入。   
目前最常用的就是动态导入。   

<Tabs>
<TabItem value="index.js" label="index.js">

```js
setTimeout(async ()=>{
    const {default:add} = await import("./add.js");
    const res = add(1, 2);
    console.log(res);
},1000)

```

</TabItem>
<TabItem value="add.js" label="add.js">

```js 
export default (x, y) => {
    return x + y;
};
```

</TabItem>
</Tabs>


来让我们看看编译之后的代码
```js title="index.js" showLineNumbers
// sucess-start
/*
*模块定义
项目中用到的模块都会被集中到这里定义
*/
var __webpack_modules__ = {};
// sucess-end

// sucess-start
/*
* require 方法定义
* __webpack_require__ webpack 自己实现 require 方法，
* 旧浏览器是默认不支持 esm，所以需要自己实现一个函数
*/
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
}
__webpack_require__.m = __webpack_modules__;
(() => {
  __webpack_require__.d = (exports, definition) => {
    for (var key in definition) {
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
})();
(() => {
  __webpack_require__.f = {};
  __webpack_require__.e = (chunkId) => {
    return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
      __webpack_require__.f[key](chunkId, promises);
      return promises;
    }, []));
  };
})();
(() => {
  __webpack_require__.u = (chunkId) => {
    return "" + chunkId + ".index.js";
  };
})();
(() => {
  __webpack_require__.g = (function () {
    if (typeof globalThis === 'object') return globalThis;
    try {
      return this || new Function('return this')();
    } catch (e) {
      if (typeof window === 'object') return window;
    }
  })();
})();
(() => {
  __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
})();
(() => {
  var inProgress = {};
  var dataWebpackPrefix = "webpack-demo:";
  __webpack_require__.l = (url, done, key, chunkId) => {
    if (inProgress[url]) { inProgress[url].push(done); return; }
    var script, needAttach;
    if (key !== undefined) {
      var scripts = document.getElementsByTagName("script");
      for (var i = 0; i < scripts.length; i++) {
        var s = scripts[i];
        if (s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
      }
    }
    if (!script) {
      needAttach = true;
      script = document.createElement('script');
      script.charset = 'utf-8';
      script.timeout = 120;
      if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
      }
      script.setAttribute("data-webpack", dataWebpackPrefix + key);
      script.src = url;
    }
    inProgress[url] = [done];
    var onScriptComplete = (prev, event) => {
      script.onerror = script.onload = null;
      clearTimeout(timeout);
      var doneFns = inProgress[url];
      delete inProgress[url];
      script.parentNode && script.parentNode.removeChild(script);
      doneFns && doneFns.forEach((fn) => (fn(event)));
      if (prev) return prev(event);
    }
    var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
    script.onerror = onScriptComplete.bind(null, script.onerror);
    script.onload = onScriptComplete.bind(null, script.onload);
    needAttach && document.head.appendChild(script);
  };
})();
(() => {
  __webpack_require__.r = (exports) => {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(exports, '__esModule', { value: true });
  };
})();
(() => {
  var scriptUrl;
  if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
  var document = __webpack_require__.g.document;
  if (!scriptUrl && document) {
    if (document.currentScript)
      scriptUrl = document.currentScript.src;
    if (!scriptUrl) {
      var scripts = document.getElementsByTagName("script");
      if (scripts.length) {
        var i = scripts.length - 1;
        while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;
      }
    }
  }
  if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
  scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
  __webpack_require__.p = scriptUrl;
})();
(() => {
  var installedChunks = { "main": 0};
  __webpack_require__.f.j = (chunkId, promises) => {
    var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
    if (installedChunkData !== 0) {
      if (installedChunkData) {
        promises.push(installedChunkData[2]);
      } else {
        if (true) {
          var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
          promises.push(installedChunkData[2] = promise);
          var url = __webpack_require__.p + __webpack_require__.u(chunkId);
          var error = new Error();
          var loadingEnded = (event) => {
            if (__webpack_require__.o(installedChunks, chunkId)) {
              installedChunkData = installedChunks[chunkId];
              if (installedChunkData !== 0) installedChunks[chunkId] = undefined;
              if (installedChunkData) {
                var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                var realSrc = event && event.target && event.target.src;
                error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                error.name = 'ChunkLoadError';
                error.type = errorType;
                error.request = realSrc;
                installedChunkData[1](error);
              }
            }
          };
          __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
        }
      }
    }
  };
  var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
    var [chunkIds, moreModules, runtime] = data;
    var moduleId, chunkId, i = 0;
    if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
      for (moduleId in moreModules) {
        if (__webpack_require__.o(moreModules, moduleId)) {
          __webpack_require__.m[moduleId] = moreModules[moduleId];
        }
      }
      if (runtime) var result = runtime(__webpack_require__);
    }
    if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
        installedChunks[chunkId][0]();
      }
      installedChunks[chunkId] = 0;
    }
  }
  var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || [];
  chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
  chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
})();
// sucess-end

// sucess-start
/*
* 入口文件
* 程序的执行入口,相当于java的main方法
*/
setTimeout(async () => {
  const { default: add } = await __webpack_require__.e("src_add_js").then(__webpack_require__.bind(__webpack_require__, "./src/add.js"));
  const res = add(1, 2);
  console.log(res);
}, 1000)
// sucess-end
```


```js title="src_add_js.index.js" showLineNumbers
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([["src_add_js"], {
  "./src/add.js"(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, {  "default": () => (__WEBPACK_DEFAULT_EXPORT__)});
    const __WEBPACK_DEFAULT_EXPORT__ = ((x, y) => {return x + y;});
  }
}]);
```

```html title="index.html"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script defer src="index.js"></script>
</head>
<body></body>
</html>
```


可以看到，入口文件的模块处理require部分 ，非常非常之长。    
这是因为涉及到懒加载部分，处理的逻辑就要相对复杂！


### 解读懒加载的处理逻辑
我们通过懒加载的请求方式（import()）引入add.js模块   
这就导致add.js会被打包切割成一个独立的文件。   
当定时器执行的时候，才会动态异步的方式去加载这个add.js模块。   
这就是懒加载的执行过程和核心！


从入口代码说起，这一步其实就是浏览器加载切割出去的按需模块add.js   
你可以通过浏览器调试查看网络请求
```js
// 加载和注册模块
await __webpack_require__.e("src_add_js")
console.log(__webpack_modules__);  // 输出所有模块内容 内有add.js
```

加载完成之后，我们就可以使用此模块了。   
```js
// 加载和注册模块
await __webpack_require__.e("src_add_js")
console.log(__webpack_modules__);  // 输出所有模块内容 内有add.js

// 获取和使用模块
const { default: add } =  __webpack_require__.bind(__webpack_require__, "./src/add.js");
const res = add(1, 2);
console.log(res);
```



**加载和注册模块**     
`__webpack_require__.e` 这个方法就是用来加载模块的。     
我们来深入一下，它是如何做到的。

```js
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => {
  return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
    __webpack_require__.f[key](chunkId, promises);
    return promises;
  }, []));
};
```

从上边就可以知道 `__webpack_require__.e`本身并不是处理加载模块的核心逻辑，他只是返回了一个加载完后的promise。      
正真处理的是`_webpack_require__.f`这个对象上的方法，依着代码找 就可以找到 `__webpack_require__.f.j`的方法，
```js showLineNumbers
var installedChunks = { "main": 0 };
__webpack_require__.f.j = (chunkId, promises) => {
  var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
  if (installedChunkData !== 0) {
    if (installedChunkData) {
      promises.push(installedChunkData[2]);
    } else {
      if (true) {
        var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
        promises.push(installedChunkData[2] = promise);
        var url = __webpack_require__.p + __webpack_require__.u(chunkId);
        var error = new Error();
        var loadingEnded = (event) => {
          if (__webpack_require__.o(installedChunks, chunkId)) {
            installedChunkData = installedChunks[chunkId];
            if (installedChunkData !== 0) installedChunks[chunkId] = undefined;
            if (installedChunkData) {
              var errorType = event && (event.type === 'load' ? 'missing' : event.type);
              var realSrc = event && event.target && event.target.src;
              error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
              error.name = 'ChunkLoadError';
              error.type = errorType;
              error.request = realSrc;
              installedChunkData[1](error);
            }
          }
        };
        __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
      }
    }
  }
};
```


仔细阅读代码 就可以发现，这也只是一个包装方法，方便提供promise而已，其加载本身的核心逻辑在第28行的 `__webpack_require__.l`中（个人推测 l是load的意思）。这个方法就是加载模块的核心了。   
```js
var inProgress = {};
var dataWebpackPrefix = "webpack-demo:";
__webpack_require__.l = (url, done, key, chunkId) => {
  if (inProgress[url]) { inProgress[url].push(done); return; }
  var script, needAttach;
  if (key !== undefined) {
    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
      var s = scripts[i];
      if (s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
    }
  }
  if (!script) {
    needAttach = true;
    script = document.createElement('script');
    script.charset = 'utf-8';
    script.timeout = 120;
    if (__webpack_require__.nc) {
      script.setAttribute("nonce", __webpack_require__.nc);
    }
    script.setAttribute("data-webpack", dataWebpackPrefix + key);
    script.src = url;
  }
  inProgress[url] = [done];
  var onScriptComplete = (prev, event) => {
    script.onerror = script.onload = null;
    clearTimeout(timeout);
    var doneFns = inProgress[url];
    delete inProgress[url];
    script.parentNode && script.parentNode.removeChild(script);
    doneFns && doneFns.forEach((fn) => (fn(event)));
    if (prev) return prev(event);
  }
  var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
  script.onerror = onScriptComplete.bind(null, script.onerror);
  script.onload = onScriptComplete.bind(null, script.onload);
  needAttach && document.head.appendChild(script);
};
```

抽丝剥茧，终于找到了最核心的部分了😭。   
可以看到webpack处理懒加载的手段也很简单，就是动态的创建script标签 后获取加载的js模块内容，再移除掉（这不就是jsonp嘛）。



注意，此时add.js这个模块只是加载了，尚未注册到 webpack整理的模块`__webpack_modules__`中，那它是怎么注册的呢？   
注册的逻辑我们要结合add.js这个打包后的模块来分析
```js title="src_add_js.index.js"
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([["src_add_js"], {
  "./src/add.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, {
      "default": () => (__WEBPACK_DEFAULT_EXPORT__)
    });
    const __WEBPACK_DEFAULT_EXPORT__ = ((x, y) => {
      return x + y;
    });
  })
}]);
```
可以看到 webpackChunkwebpack_demo就是整个项目的打包文件们的全局变量 看名字都能猜出来 `webpack_demo_Chunks`。还有那个push方法，应该就是再做注入的操作了。

在回过头来看入口entry打包文件中，最后几行（160-183行），可以看到当加载模块文件add.js时候，模块文件里的代码也将执行（即push到全局模块变量webpackChunkwebpack_demo中）。   
这个push方法又被重新定义过，将会执行到`webpackJsonpCallback`方法上。    
webpackJsonpCallback内部就执行了注册__webpack_modules__ （通过__webpack_require__.m） 

```js
 var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
  var [chunkIds, moreModules, runtime] = data;
  var moduleId, chunkId, i = 0;
  if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
    for (moduleId in moreModules) {
      if (__webpack_require__.o(moreModules, moduleId)) {
        __webpack_require__.m[moduleId] = moreModules[moduleId];
      }
    }
    if (runtime) var result = runtime(__webpack_require__);
  }
  if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
  for (; i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
      installedChunks[chunkId][0]();
    }
    installedChunks[chunkId] = 0;
  }
}
var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); // 貌似无用 忽略
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
```